package online.heycm.platform.web.common.encrypt;

import lombok.SneakyThrows;
import online.heycm.platform.common.tools.Jackson;
import online.heycm.platform.web.common.annotation.EncryptField;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * RequestBody 加密拦截
 *
 * @author hey
 * @version 1.0
 * @date 2024/1/16 15:59
 */
public abstract class AbstractEncryptRequestBodyAdvice implements RequestBodyAdvice {

    /**
     * 加密
     *
     * @param content 内容
     * @return
     */
    protected abstract String encrypt(String content);

    /**
     * 获取加密字段
     *
     * @param type 类型
     * @return
     * @throws ClassNotFoundException
     */
    private List<String> getEncryptFieldNames(Type type) throws ClassNotFoundException {
        String typeName = type.getTypeName();
        Class<?> typeClass = Class.forName(typeName);
        Field[] fields = typeClass.getDeclaredFields();
        List<String> encryptFields = new ArrayList<>(fields.length);
        for (Field field : fields) {
            EncryptField annotation = field.getAnnotation(EncryptField.class);
            if (annotation != null) {
                encryptFields.add(field.getName());
            }
        }
        return encryptFields;
    }

    @SneakyThrows
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return !getEncryptFieldNames(type).isEmpty();
    }

    @SneakyThrows
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter,
                                           Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        String body = IOUtils.toString(httpInputMessage.getBody(), StandardCharsets.UTF_8);
        Map<String, Object> bodyMap = Jackson.toObject(body, Map.class);
        if (bodyMap != null) {
            final List<String> encryptFieldNames = getEncryptFieldNames(type);
            for (String encryptFieldName : encryptFieldNames) {
                Object originalValue = bodyMap.get(encryptFieldName);
                if (originalValue != null) {
                    bodyMap.put(encryptFieldName, this.encrypt(originalValue.toString()));
                }
            }
            body = Jackson.toJson(bodyMap);
        }
        InputStream inputStream = IOUtils.toInputStream(body, StandardCharsets.UTF_8);
        return new HttpInputMessage() {
            @Override
            public InputStream getBody() throws IOException {
                return inputStream;
            }

            @Override
            public HttpHeaders getHeaders() {
                return httpInputMessage.getHeaders();
            }
        };
    }

    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type,
                                Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }

    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter,
                                  Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }
}
